#include "Win32Window.h"
#include "Logger.h"
#include "IGraphicContext.h"
#include "GraphicsDevice.h"
#include "IContext.h"
#include "Exception.h"


NS_B2D_BEGIN

Win32Window::Win32Window(const WindowOptions& _opts)
	: mWindowHandle(nullptr)
	, mExternalRC(false)
{
	WindowOptions opts = _opts;

	// Create window
	create(opts);

	// Create context
	auto g = GetContext().getInstance<IGraphicsDevice>();
	if(g == nullptr)
	{
		B2D_THROW(Exception::INTERNAL_ERROR_EXCEPTION,
			"Cant't create graphics context for the window because the graphics device "
			"hasn't been registered.");
	}
	mContext = g->_createGraphicsContext();
	mContext->initialise(*this, opts);

	// Set these after creating context, as they might have been
	// modified
	mDepthBits = opts.depthBits;
	mColorBits = opts.colorBits;
	B2D_LOG_DEBUG("Win32Window window created (HWND=%d).\n", mWindowHandle);
}

Win32Window::Win32Window(WindowHandle externalHandle)
	: mWindowHandle(nullptr)
	, mExternalRC(true)
{
	// Create window
	create(externalHandle);
	B2D_LOG_DEBUG("Win32Window external window created (HWND=%d).\n", mWindowHandle);
}

Win32Window::~Win32Window(void)
{
	if( mContext != nullptr )
	{
		mContext->destroy();
		SafeDelete(mContext);
	}
	B2D_LOG_DEBUG("Win32Window was destroyed (HWND=%d).\n", mWindowHandle);
}


WindowHandle Win32Window::getWindowHandle(void) const
{
	return static_cast<WindowHandle>(mWindowHandle);
}

void* Win32Window::getRTHandle(void) const
{
	// TODO SP - return handle
	return (void*)0;
}

void Win32Window::resize(const size2& size)
{
	BaseWindow::resize(size);
	// TODO SP - reize
}

void Win32Window::move(const ivec2& pos)
{
	BaseWindow::move(pos);
	// TODO SP - set position
}

void Win32Window::setTitle(const string& title)
{
	BaseWindow::setTitle(title);
	// TODO SP - set title
}

void Win32Window::create(WindowHandle handle)
{
	mExternalWindow = true;

	// Ensure the window handle is good
	mWindowHandle = static_cast<HWND>(handle);
	if(mWindowHandle == nullptr)
	{
		B2D_THROW(Exception::INVALID_ARGUMENT_EXCEPTION,
			"The Win32 handle provided is not valid");
	}

	// Get handle and store metrics
	RECT wndSize;
	GetWindowRect(mWindowHandle, &wndSize);
	mPosition.x = wndSize.left;
	mPosition.y = wndSize.top;
	mSize.x = static_cast<uint>( std::max(0, int(wndSize.right - wndSize.left)) );
	mSize.y = static_cast<uint>( std::max(0, int(wndSize.bottom - wndSize.top)) );
	mFullscreen = IsWindowVisible(mWindowHandle) || IsIconic(mWindowHandle) || !IsZoomed(mWindowHandle);
}

void Win32Window::create(WindowOptions& opts)
{
	mExternalWindow = false;
	mFullscreen = opts.fullscreen;
	mTitle = opts.title;

	HINSTANCE hInstance = GetHINSTANCE();
	const char* className = opts.title.c_str();

	// Register Class
	WNDCLASSEX wcex = {};
	wcex.cbSize			= sizeof(WNDCLASSEX);
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= nullptr;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= nullptr;
	wcex.lpszMenuName	= nullptr;
	wcex.lpszClassName	= className;
	//wcex.hIconSm		= 0;

	if( !RegisterClassEx(&wcex) )
	{
		B2D_THROW(Exception::SYSTEM_EXCEPTION,
			"Could not register Win32 window class");
	}

	// Get requested size
	RECT wndSize =
	{
		opts.position.x,
		opts.position.y,
		opts.position.x + opts.size.x,
		opts.position.y + opts.size.y
	};
	// Work out style
	DWORD style = (mFullscreen
		? WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS
		: WS_OVERLAPPED | WS_SYSMENU| WS_MINIMIZEBOX | WS_CAPTION );

	// Adjust size and get real values
	AdjustWindowRect(&wndSize, style, FALSE);
	const int wndWidth	= wndSize.right - wndSize.left;
	const int wndHeight = wndSize.bottom - wndSize.top;
	const int wndLeft	= (mFullscreen == true ? 0 : std::max(0, (GetSystemMetrics(SM_CXSCREEN) - wndWidth) / 2));
	const int wndTop	= (mFullscreen == true ? 0 : std::max(0, (GetSystemMetrics(SM_CYSCREEN) - wndHeight) / 2));

	// Create window
	mWindowHandle = CreateWindow(
		className,
		opts.title.c_str(),
		style,
		wndLeft,
		wndTop,
		wndWidth,
		wndHeight,
		nullptr,
		nullptr,
		hInstance,
		nullptr);

	// Store metrics
	opts.position.x = wndLeft;
	opts.position.y = wndTop;
	opts.size.x = wndWidth;
	opts.size.y = wndHeight;

	mSize = opts.size;
	mPosition = opts.position;

	ShowWindow(mWindowHandle, SW_SHOW);
	UpdateWindow(mWindowHandle);
	MoveWindow(mWindowHandle, wndLeft, wndTop, wndWidth, wndHeight, TRUE);
}


HINSTANCE Win32Window::GetHINSTANCE(void)
{
	HINSTANCE hInstance = GetModuleHandle(nullptr);
	return hInstance;
}

LRESULT CALLBACK Win32Window::WndProc(HWND hWnd, size_t message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_CLOSE:
		{
			//Application::Get().quit();
			break;
		}
		case WM_DESTROY:
		{
			PostQuitMessage(0);
			break;
		}
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

void Win32Window::_MessagePump(void)
{
	MSG msg;
    while( PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE) )
    {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
    }
}

NS_B2D_END